package cn.lili.controller.wxpay;

import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.properties.ApiProperties;
import cn.lili.common.properties.DomainProperties;
import cn.lili.common.utils.IpUtils;
import cn.lili.common.utils.SnowFlake;
import cn.lili.modules.payment.kit.core.utils.DateTimeZoneUtil;
import cn.lili.modules.payment.kit.plugin.wechat.WechatV3Api;
import cn.lili.modules.store.entity.dos.StoreSft;
import cn.lili.modules.store.service.StoreSftService;
import cn.lili.modules.system.entity.dos.Setting;
import cn.lili.modules.system.entity.dto.payment.WechatPaymentSetting;
import cn.lili.modules.system.entity.enums.SettingEnum;
import cn.lili.modules.system.service.SettingService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jfinal.json.FastJson;
import com.jfinal.kit.Ret;
import com.jfinal.weixin.sdk.api.ApiResult;
import com.jfinal.weixin.sdk.api.MenuApi;
import com.jfinal.weixin.sdk.api.QrcodeApi;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.math3.util.MathUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.TimeUnit;

@SuppressWarnings("DuplicatedCode")
@Slf4j
@Controller
@RequestMapping({"/buyer/so", "/buyer/wx", "/buyer/wxstatistics", "/buyer/news"})
public class CreateOrderController extends BaseController {

    @Autowired
    private SettingService settingService;
    @Autowired
    private StoreSftService storeSftService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * API域名
     */
    @Autowired
    private ApiProperties apiProperties;
    @Autowired
    private DomainProperties domainProperties;

    @RequestMapping("/{key}")
    @ResponseBody
    public Ret index(@PathVariable String key, HttpServletRequest request) throws Exception {
        String content = stringRedisTemplate.opsForValue().get("wxpay:" + key);
        if (StringUtils.isNotEmpty(content)) {
            if (BooleanUtils.isNotTrue(stringRedisTemplate.hasKey("wx:pay:paid:" + key))) {
                Ret param = FastJson.getJson().parse(content, Ret.class);
                String type = param.getStr("type");
                switch (type) {
                    case "h5":
                        String url = doH5(key, param, request);
                        return Ret.ok("url", url).set("type", "h5");
                    default:
                        break;
                }
            } else {
                return Ret.fail("code", "paid");
            }

        }
        return Ret.fail("code", "404");
    }


    /**
     * 退款
     */
    @RequestMapping(path = "/refund", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public Ret refund(@RequestParam String p, HttpServletRequest request) {
        try {
            p = base64Decode(StringUtils.trim(p));
            String content = decrypt(StringUtils.trim(p));
            log.info("退款内容：{}", content);
            Ret param = FastJson.getJson().parse(content, Ret.class);
            String orderId = param.getStr("orderId");
            WechatPaymentSetting setting = wechatPaymentSetting();
            JSONObject complaint = param.getAs("complaint");
            JSONArray complaintOrderInfo = complaint.getJSONArray("complaint_order_info");
            log.info("退款单数：{}", complaintOrderInfo.size());
            for (Object o : complaintOrderInfo) {
                JSONObject orderInfo = (JSONObject) o;
                int amount = orderInfo.getInteger("amount");
                Ret response = WechatV3Api.response(complaint.getString("complaint_id"), complaint.getString("complainted_mchid"));
                log.info("回复用户结果：{}", response);
                Ret updateRefundProgress = WechatV3Api.updateRefundProgress(complaint.getString("complaint_id"), "APPROVE");
                log.info("更新投诉审批结果：{}", updateRefundProgress);
                Ret refundParam = Ret.create().set("sp_appid", setting.getServiceAppId())
                        .set("sub_mchid", complaint.getString("complainted_mchid"))
                        .set("transaction_id", orderInfo.getString("transaction_id"))
                        .set("out_refund_no", orderInfo.getString("out_trade_no"))
                        .set("reason", "协商退款")
                        .set("refund_account", StringUtils.defaultIfEmpty(param.getStr("refundAccount"), "REFUND_SOURCE_SUB_MERCHANT"))
                        .set("notify_url", apiProperties.getBuyer() + "/buyer/payment/so/notify/WECHAT/refund")
                        .set("amount", Ret.create().set("currency", "CNY").set("refund", amount).set("total", amount));
                log.info("微信退款参数 {}", JSON.toJSONString(refundParam));
                Ret result = WechatV3Api.refundApply(refundParam);
                log.info("微信退款响应 {}", result);

                //退款申请成功
                if (result.isOk()) {
                    Ret completeRefund = WechatV3Api.completeRefund(complaint.getString("complaint_id"), complaint.getString("complainted_mchid"));
                    log.info("完成投诉处理：{}", completeRefund);
                    log.info("\n{}退款成功 {} ", orderId, orderInfo.getString("out_trade_no"));
                } else {
                    log.info("\n{}退款失败 {} ", orderId, orderInfo.getString("out_trade_no"));
                }
            }
            return Ret.ok();
        } catch (Exception e) {
            log.error("退款失败", e);
            return (Ret.fail("msg", e.getMessage()));
        }
    }

    /**
     * 退款
     */
    @RequestMapping(path = "/refund1", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public Ret refund1(@RequestParam String p, HttpServletRequest request) {
        try {
            p = base64Decode(StringUtils.trim(p));
            String content = decrypt(StringUtils.trim(p));
            log.info("退款内容：{}", content);
            Ret param = FastJson.getJson().parse(content, Ret.class);
            String orderId = param.getStr("orderId");
            double refundRate = ObjectUtils.defaultIfNull(param.getDouble("refundRate"), 1d);
            WechatPaymentSetting setting = wechatPaymentSetting();
            String c = stringRedisTemplate.opsForValue().get("wx:pay:paid:" + orderId);
            log.info("退款账户：{}", param.getStr("refundAccount"));
            if (StringUtils.isNotEmpty(c)) {
                Ret orderInfo = FastJson.getJson().parse(c, Ret.class);
                boolean isCombine = ObjectUtils.defaultIfNull(param.getBoolean("isCombine"), false);
                if (isCombine) {
                    JSONArray subOrders = orderInfo.getAs("sub_orders");
                    int successNum = 0;
                    for (Object subOrder : subOrders) {
                        JSONObject obj = (JSONObject) subOrder;
                        int totalAmount = obj.getJSONObject("amount").getIntValue("total_amount");
                        int refundAmount = (int) (totalAmount * refundRate);
                        String orderSuffix = "";
                        if (MathUtils.equals(0.3, refundRate)) {
                            refundAmount = (int) (totalAmount * 0.7);
                            refundAmount = totalAmount - refundAmount;
                            orderSuffix = "_3";
                        } else if (MathUtils.equals(0.7, refundRate)) {
                            orderSuffix = "_7";
                        }
                        Ret refundParam = Ret.create().set("sp_appid", setting.getServiceAppId())
                                .set("sub_mchid", obj.getString("sub_mchid"))
                                .set("transaction_id", obj.getString("transaction_id"))
                                .set("out_refund_no", obj.getString("out_trade_no") + orderSuffix)
                                .set("reason", "协商退款")
                                .set("refund_account", StringUtils.defaultIfEmpty(param.getStr("refundAccount"), "REFUND_SOURCE_SUB_MERCHANT"))
                                .set("amount", Ret.create().set("currency", "CNY").set("refund", refundAmount).set("total", totalAmount));
                        log.info("微信退款参数 {}", JSON.toJSONString(refundParam));
                        Ret result = WechatV3Api.refundApply(refundParam);
                        log.info("微信退款响应 {}", result);
                        if (result.isOk()) {
                            successNum++;
                        }
                    }
                    if (successNum == 0) {
                        return Ret.fail("msg", "退款失败");
                    } else if (successNum == subOrders.size()) {
                        return Ret.ok("msg", "已全部退款");
                    } else {
                        return Ret.fail("msg", "部分退款，订单数量：" + subOrders.size() + "退款成功数量：" + successNum);
                    }
                } else {
                    BigDecimal amount = BigDecimal.valueOf(Double.parseDouble(
                            param.getStr("amount")));
                    StoreSft sft = storeSftService.getByStoreId(param.getStr("storeId"));
                    Ret refundParam = Ret.create().set("sp_appid", setting.getServiceAppId())
                            .set("sub_mchid", sft.getSubMchid())
                            .set("transaction_id", orderInfo.getStr("transaction_id"))
                            .set("out_refund_no", orderInfo.getStr("out_trade_no"))
                            .set("reason", "协商退款")
                            .set("refund_account", StringUtils.defaultIfEmpty(param.getStr("refundAccount"), "REFUND_SOURCE_SUB_MERCHANT"))
                            .set("amount", Ret.create().set("currency", "CNY").set("refund", amount.multiply(BigDecimal.valueOf(100L)).intValue()).set("total", amount.multiply(BigDecimal.valueOf(100L)).intValue()));
                    log.info("微信退款参数 {}", JSON.toJSONString(refundParam));
                    Ret result = WechatV3Api.refundApply(refundParam);
                    log.info("微信退款响应 {}", result);

                    return result;
                }
            }
            return Ret.ok();
        } catch (Exception e) {
            log.error("退款失败", e);
            return (Ret.fail("msg", e.getMessage()));
        }
    }

    @RequestMapping(path = "/completeProfitsharing", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public Ret completeProfitsharing(@RequestParam String p, HttpServletRequest request) {
        try {
            p = base64Decode(StringUtils.trim(p));
            String content = decrypt(StringUtils.trim(p));
            log.info("微信解冻分账：{}", content);
            Ret param = FastJson.getJson().parse(content, Ret.class);
            String orderId = param.getStr("orderId");
            String c = stringRedisTemplate.opsForValue().get("wx:pay:paid:" + orderId);
            if (StringUtils.isNotEmpty(c)) {
                Ret orderInfo = FastJson.getJson().parse(c, Ret.class);
                StoreSft sft = storeSftService.getByStoreId(param.getStr("storeId"));

                Ret result = WechatV3Api.completeProfitsharing(sft.getSubMchid(), orderInfo.getStr("transaction_id"), orderInfo.getStr("out_trade_no"), "完成分账");
                log.info("微信解冻分账响应 {}", result);

                return result;
            }
            return Ret.ok();
        } catch (Exception e) {
            log.error("退款失败", e);
            return (Ret.fail("msg", e.getMessage()));
        }
    }

    /**
     * 预处理
     */
    @RequestMapping(path = "/pre", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public Ret pre(@RequestParam String p, HttpServletRequest request) {
        try {
            p = base64Decode(StringUtils.trim(p));
            String content = decrypt(StringUtils.trim(p));
            Ret param = FastJson.getJson().parse(content, Ret.class);
            String type = param.getStr("type");
            log.info("预下单请求内容2：{}", content);

            String key = RandomStringUtils.randomAlphanumeric(10);

            String outTradeNo = SnowFlake.getIdStr();
            stringRedisTemplate.opsForValue().set("wxpay:" + key, param.set("key", key).set("outTradeNo", outTradeNo).toJson(), 600, TimeUnit.SECONDS);
            stringRedisTemplate.opsForValue().set("wxpay:" + param.getStr("orderId"), param.set("key", key).set("outTradeNo", outTradeNo).toJson(), 30, TimeUnit.DAYS);
            JSONArray orders = param.getAs("orders");
            if (CollectionUtils.isNotEmpty(orders)) {
                for (Object order : orders) {
                    JSONObject obj = (JSONObject) order;
                    stringRedisTemplate.opsForValue().set("wxpay:idref:" + obj.getString("outTradeNo"), param.getStr("orderId"), 30, TimeUnit.DAYS);
                }
            }

            log.info("\n生成预下单地址:{}", param.toJson());
            log.info("\n生成预下单地址成功:{}", getBaseUrl(request) + "/wx/user/order/" + key);
            WechatPaymentSetting setting = wechatPaymentSetting();

            Ret result = Ret.ok("url", domainProperties.getWap() + "/pages/go/index?key=" + key);
            switch (type) {
                case "h5":
                    result.set("type", "link");
                    //String url = doH5(param, request);
                    return result;
                case "native":
                    String url = doNative(key, param, request);
                    return Ret.ok("url", url).set("type", "qr");
                case "mpn":
                    ApiResult temporary = QrcodeApi.createTemporary(3 * 60, key);
                    url = temporary.get("url");
                    return Ret.ok("url", url).set("type", "qr");
                default:
                    break;
            }
        } catch (Exception e) {
            log.error("生成预下单地址失败", e);
            return (Ret.fail("msg", e.getMessage()));
        }
        return Ret.fail();
    }

    public String doH5(String key, Ret param, HttpServletRequest request) throws Exception {
        String orderId = param.getStr("orderId");
        String redisPayInfoKey = "wxpay:payInfo:" + orderId;
        boolean isCombine = ObjectUtils.defaultIfNull(param.getBoolean("isCombine"), false);
        String url;

        if (BooleanUtils.isNotTrue(stringRedisTemplate.hasKey(redisPayInfoKey))) {
            WechatPaymentSetting setting = wechatPaymentSetting();
            String outTradeNo = param.getStr("outTradeNo");
            //过期时间
            String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
            Ret params;
            if (isCombine) {
                JSONArray orders = param.getAs("orders");
                List<Ret> subOrders = new ArrayList<>();
                orders.stream().map(o -> (JSONObject) o).forEach(o -> {
                    StoreSft sft = storeSftService.getByStoreId(o.getString("storeId"));
                    int amount = BigDecimal.valueOf(Double.parseDouble(o.getString("preAmount"))).multiply(BigDecimal.valueOf(100L)).intValue();

                    Ret subOrderInfo =
                            Ret.create()
                                    .set("mchid", setting.getMchId())
                                    .set("attach", Base64.getEncoder().encodeToString(Ret.create("orderId", orderId).set("key", key).toJson().getBytes(StandardCharsets.UTF_8)))
                                    .set("amount", Ret.create("total_amount", amount).set("currency", "CNY"))
                                    .set("out_trade_no", o.getString("outTradeNo"))
                                    .set("sub_mchid", sft.getSubMchid()).set("description", "冠佩商城-在线支付");

                    //是否分账
                    if ("1".equals(setting.getIsProfitSharing())) {
                        Ret profitSharing = Ret.create("profit_sharing", true);
                        Integer subsidyAmount = o.getInteger("subsidy_amount");
                        if (subsidyAmount != null && subsidyAmount > 0) {
                            profitSharing.set("subsidy_amount", subsidyAmount);
                        }
                        subOrderInfo.set("settle_info", profitSharing);
                    }
                    subOrders.add(subOrderInfo);
                });
                params = Ret.create()
                        .set("combine_appid", setting.getServiceAppId())
                        .set("combine_mchid", setting.getMchId())
                        .set("combine_out_trade_no", outTradeNo)
                        .set("notify_url", apiProperties.getBuyer() + "/buyer/payment/so/notify/WECHAT/combine")
                        .set("scene_info", Ret.create("device_id", "WEB").set("payer_client_ip", IpUtils.getIpAddress(request)).set("h5_info", Ret.create().set("type", "Wap")))
                        .set("time_expire", timeExpire)
                        .set("sub_orders", subOrders);
            } else {

                StoreSft sft = storeSftService.getByStoreId(param.getStr("storeId"));
                int amount = BigDecimal.valueOf(Double.parseDouble(
                        param.getStr("preAmount"))).multiply(BigDecimal.valueOf(100L)).intValue();

                params = Ret.create()
                        .set("sp_appid", setting.getServiceAppId())
                        .set("sp_mchid", setting.getMchId())
                        .set("sub_mchid", sft.getSubMchid())
                        .set("description", "在线支付")
                        .set("out_trade_no", outTradeNo)
                        .set("time_expire", timeExpire)
                        .set("attach", Base64.getEncoder().encodeToString(Ret.create("orderId", orderId).set("key", key).toJson().getBytes(StandardCharsets.UTF_8)))
                        .set("notify_url", apiProperties.getBuyer() + "/buyer/payment/so/notify/WECHAT")
                        .set("amount", Ret.create("total", amount))
                        .set("scene_info", Ret.create("payer_client_ip", IpUtils.getIpAddress(request)).set("h5_info", Ret.create("type", "Wap")));

                //是否分账
                if ("1".equals(setting.getIsProfitSharing())) {
                    Ret profitSharing = Ret.create("profit_sharing", true);
                    Integer subsidyAmount = param.getInt("subsidy_amount");
                    if (subsidyAmount != null && subsidyAmount > 0) {
                        profitSharing.set("subsidy_amount", subsidyAmount);
                    }
                    params.set("settle_info", profitSharing);
                }
            }

            log.info("统一下单参数 {} 是否合单 {}", params.toJson(), isCombine);
            Ret result = WechatV3Api.sPayH5(isCombine, params);
            log.info("统一下单响应 {} 是否合单 {}", result, isCombine);

            if (result.isOk()) {
                url = result.getStr("h5_url");
            } else {
                log.error("下单失败：{}", result);
                url = "";
            }
            log.info("H5下单URL：{}", url);
            stringRedisTemplate.opsForValue().set("wxpay:idref:" + orderId, outTradeNo);
            stringRedisTemplate.opsForValue().set("wxpay:notify:" + outTradeNo, param.getStr("notifyUrl"), 86400, TimeUnit.SECONDS);
            stringRedisTemplate.opsForValue().set(redisPayInfoKey, url, 86400, TimeUnit.SECONDS);
        } else {
            url = stringRedisTemplate.opsForValue().get(redisPayInfoKey);
        }

        return url;

    }

    public String doNative(String key, Ret param, HttpServletRequest request) throws Exception {
        String orderId = param.getStr("orderId");
        String redisPayInfoKey = "wxpay:payInfo:" + orderId;
        boolean isCombine = ObjectUtils.defaultIfNull(param.getBoolean("isCombine"), false);
        String url;

        if (BooleanUtils.isNotTrue(stringRedisTemplate.hasKey(redisPayInfoKey))) {
            WechatPaymentSetting setting = wechatPaymentSetting();
            String outTradeNo = param.getStr("outTradeNo");
            //过期时间
            String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
            Ret params;
            if (isCombine) {
                JSONArray orders = param.getAs("orders");
                List<Ret> subOrders = new ArrayList<>();
                orders.stream().map(o -> (JSONObject) o).forEach(o -> {
                    StoreSft sft = storeSftService.getByStoreId(o.getString("storeId"));
                    int amount = BigDecimal.valueOf(Double.parseDouble(o.getString("preAmount"))).multiply(BigDecimal.valueOf(100L)).intValue();

                    Ret subOrderInfo =
                            Ret.create()
                                    .set("mchid", setting.getMchId())
                                    .set("attach", Base64.getEncoder().encodeToString(Ret.create("orderId", orderId).set("key", key).toJson().getBytes(StandardCharsets.UTF_8)))
                                    .set("amount", Ret.create("total_amount", amount).set("currency", "CNY"))
                                    .set("out_trade_no", o.getString("outTradeNo"))
                                    .set("sub_mchid", sft.getSubMchid()).set("description", "冠佩商城-在线支付");

                    //是否分账
                    if ("1".equals(setting.getIsProfitSharing())) {

                        Ret profitSharing = Ret.create("profit_sharing", true);
                        Integer subsidyAmount = o.getInteger("subsidy_amount");
                        if (subsidyAmount != null && subsidyAmount > 0) {
                            profitSharing.set("subsidy_amount", subsidyAmount);
                        }
                        subOrderInfo.set("settle_info", profitSharing);
                    }
                    subOrders.add(subOrderInfo);
                });
                params = Ret.create()
                        .set("combine_appid", setting.getServiceAppId())
                        .set("combine_mchid", setting.getMchId())
                        .set("combine_out_trade_no", outTradeNo)
                        .set("notify_url", apiProperties.getBuyer() + "/buyer/payment/so/notify/WECHAT/combine")
                        .set("time_expire", timeExpire)
                        .set("sub_orders", subOrders);
            } else {

                StoreSft sft = storeSftService.getByStoreId(param.getStr("storeId"));
                int amount = BigDecimal.valueOf(Double.parseDouble(
                        param.getStr("preAmount"))).multiply(BigDecimal.valueOf(100L)).intValue();

                params = Ret.create()
                        .set("sp_appid", setting.getServiceAppId())
                        .set("sp_mchid", setting.getMchId())
                        .set("sub_mchid", sft.getSubMchid())
                        .set("description", "在线支付")
                        .set("out_trade_no", outTradeNo)
                        .set("time_expire", timeExpire)
                        .set("attach", Base64.getEncoder().encodeToString(Ret.create("orderId", orderId).set("key", key).toJson().getBytes(StandardCharsets.UTF_8)))
                        .set("notify_url", apiProperties.getBuyer() + "/buyer/payment/so/notify/WECHAT")
                        .set("amount", Ret.create().set("total", amount));
                //是否分账
                if ("1".equals(setting.getIsProfitSharing())) {
                    Ret profitSharing = Ret.create("profit_sharing", true);
                    Integer subsidyAmount = param.getInt("subsidy_amount");
                    if (subsidyAmount != null && subsidyAmount > 0) {
                        profitSharing.set("subsidy_amount", subsidyAmount);
                    }
                    params.set("settle_info", profitSharing);
                }
            }

            log.info("统一下单参数 {} 是否合单 {}", params.toJson(), isCombine);
            Ret result = WechatV3Api.sPayNative(isCombine, params);
            log.info("统一下单响应 {} 是否合单 {}", result, isCombine);

            if (result.isOk()) {
                url = result.getStr("code_url");
            } else {
                log.error("下单失败：{}", result);
                url = "";
            }
            log.info("Native下单URL：{}", url);
            stringRedisTemplate.opsForValue().set("wxpay:idref:" + orderId, outTradeNo);
            stringRedisTemplate.opsForValue().set("wxpay:notify:" + outTradeNo, param.getStr("notifyUrl"), 86400, TimeUnit.SECONDS);
            stringRedisTemplate.opsForValue().set(redisPayInfoKey, url, 86400, TimeUnit.SECONDS);
        } else {
            url = stringRedisTemplate.opsForValue().get(redisPayInfoKey);
        }

        return url;

    }

    @RequestMapping(path = "/createMenu")
    @ResponseBody
    public ApiResult createMenu(HttpServletRequest request) {
        //        MenuApi.deleteMenu();
        ApiResult menus = MenuApi.createMenu(request.getParameter("menus"));
        return menus;
    }

    @RequestMapping(path = "/deleteMenu", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ApiResult deleteMenu() {
        return MenuApi.deleteMenu();
    }

    private WechatPaymentSetting wechatPaymentSetting() {
        try {
            Setting systemSetting = settingService.get(SettingEnum.WECHAT_PAYMENT.name());
            return JSON.parseObject(systemSetting.getSettingValue()).toJavaObject(WechatPaymentSetting.class);
        } catch (Exception e) {
            log.error("微信支付暂不支持", e);
            throw new ServiceException(ResultCode.PAY_NOT_SUPPORT);
        }
    }
}
